// @ts-nocheck import { useParams } from 'common' import { AlertTriangle, GitBranchIcon, X } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useCallback, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { Button, cn, NavMenu, NavMenuItem } from 'ui' import { Admonition } from 'ui-patterns' import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal' import { useIsPgDeltaDiffEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { MergeActions, MergeSubtitle, MergeTitle, } from '@/components/interfaces/Branching/MergeRequest' import { DatabaseDiffPanel } from '@/components/interfaces/BranchManagement/DatabaseDiffPanel' import { EdgeFunctionsDiffPanel } from '@/components/interfaces/BranchManagement/EdgeFunctionsDiffPanel' import { OutOfDateNotice } from '@/components/interfaces/BranchManagement/OutOfDateNotice' import { WorkflowLogsCard } from '@/components/interfaces/BranchManagement/WorkflowLogsCard' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' import { ProjectLayoutWithAuth } from '@/components/layouts/ProjectLayout' import { ScaffoldContainer } from '@/components/layouts/Scaffold' import ProductEmptyState from '@/components/to-be-cleaned/ProductEmptyState' import { InlineLink } from '@/components/ui/InlineLink' import { useBranchDeleteMutation } from '@/data/branches/branch-delete-mutation' import { useBranchMergeMutation } from '@/data/branches/branch-merge-mutation' import { useBranchPushMutation } from '@/data/branches/branch-push-mutation' import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation' import { useBranchesQuery } from '@/data/branches/branches-query' import { useProjectGitHubConnectionQuery } from '@/data/integrations/github-connections-query' import { useProjectDetailQuery } from '@/data/projects/project-detail-query' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useBranchMergeDiff } from '@/hooks/branches/useBranchMergeDiff' import { useWorkflowManagement } from '@/hooks/branches/useWorkflowManagement' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import type { NextPageWithLayout } from '@/types' const MergePage: NextPageWithLayout = () => { const router = useRouter() const { ref, workflow_run_id: currentWorkflowRunId } = useParams() const { data: project } = useSelectedProjectQuery() const { data: selectedOrg } = useSelectedOrganizationQuery() const pgDeltaDiffEnabled = useIsPgDeltaDiffEnabled() const [isSubmitting, setIsSubmitting] = useState(false) const [workflowFinalStatus, setWorkflowFinalStatus] = useState<'SUCCESS' | 'FAILED' | null>(null) const [showConfirmDialog, setShowConfirmDialog] = useState(false) const isBranch = project?.parent_project_ref !== undefined const parentProjectRef = project?.parent_project_ref const { data: parentProject } = useProjectDetailQuery({ ref: parentProjectRef }) const { data: ghConnection } = useProjectGitHubConnectionQuery({ ref: parentProjectRef }) const { data: branches } = useBranchesQuery( { projectRef: parentProjectRef }, { refetchOnMount: 'always', refetchOnWindowFocus: true, staleTime: 0, } ) const currentBranch = branches?.find((branch) => branch.project_ref === ref) const mainBranch = branches?.find((branch) => branch.is_default) const { diffContent, isDatabaseDiffLoading, isDatabaseDiffRefetching, databaseDiffError: diffError, refetchDatabaseDiff: refetchDiff, edgeFunctionsDiff, isBranchOutOfDateMigrations, hasEdgeFunctionModifications, missingFunctionsCount, hasMissingFunctions, outOfDateFunctionsCount, hasOutOfDateFunctions, isBranchOutOfDateOverall, missingMigrationsCount, modifiedFunctionsCount, } = useBranchMergeDiff({ currentBranchRef: ref, parentProjectRef, currentBranchConnectionString: project?.connectionString || undefined, parentBranchConnectionString: parentProject?.connectionString || undefined, currentBranchCreatedAt: currentBranch?.created_at, }) const { mutate: updateBranch } = useBranchUpdateMutation({ onError: (error) => { toast.error(`Failed to update branch: ${error.message}`) }, }) const clearDiffsOptimistically = edgeFunctionsDiff.clearDiffsOptimistically const handleCurrentBranchWorkflowComplete = useCallback( (status: 'SUCCESS' | 'FAILED') => { setWorkflowFinalStatus(status) refetchDiff() clearDiffsOptimistically() }, [refetchDiff, clearDiffsOptimistically] ) const handleParentBranchWorkflowComplete = useCallback( (status: 'SUCCESS' | 'FAILED') => { setWorkflowFinalStatus(status) refetchDiff() clearDiffsOptimistically() if (ref && parentProjectRef && currentBranch?.review_requested_at) { updateBranch( { branchRef: ref, projectRef: parentProjectRef, requestReview: false, }, { onSuccess: () => toast.success('Branch updated successfully'), } ) } }, [ refetchDiff, clearDiffsOptimistically, parentProjectRef, ref, updateBranch, currentBranch?.review_requested_at, ] ) const { run: currentBranchWorkflow, logs: currentBranchLogs } = useWorkflowManagement({ workflowRunId: currentWorkflowRunId, projectRef: ref, onWorkflowComplete: handleCurrentBranchWorkflowComplete, }) const { run: parentBranchWorkflow, logs: parentBranchLogs } = useWorkflowManagement({ workflowRunId: currentWorkflowRunId, projectRef: parentProjectRef, onWorkflowComplete: handleParentBranchWorkflowComplete, }) const currentWorkflowRun = currentBranchWorkflow || parentBranchWorkflow const workflowRunLogs = currentBranchLogs || parentBranchLogs const hasCurrentWorkflowFailed = workflowFinalStatus === 'FAILED' const hasCurrentWorkflowCompleted = workflowFinalStatus === 'SUCCESS' const isWorkflowRunning = currentWorkflowRun?.status === 'RUNNING' const addWorkflowRun = useCallback( (workflowRunId: string) => { router.push({ pathname: router.pathname, query: { ...router.query, workflow_run_id: workflowRunId }, }) }, [router] ) const clearWorkflowRun = useCallback(() => { const { workflow_run_id, ...queryWithoutWorkflowId } = router.query router.push({ pathname: router.pathname, query: queryWithoutWorkflowId, }) }, [router]) const { mutate: pushBranch, isPending: isPushing } = useBranchPushMutation({ onSuccess: (data) => { toast.success('Branch update initiated!') if (data?.workflow_run_id) { addWorkflowRun(data.workflow_run_id) } // Track branch update sendEvent({ action: 'branch_updated', properties: { source: 'merge_page', }, groups: { project: parentProjectRef ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown', }, }) }, onError: (error) => { toast.error(`Failed to update branch: ${error.message}`) }, }) const { mutate: sendEvent } = useSendEventMutation() const { mutate: mergeBranch, isPending: isMerging } = useBranchMergeMutation({ onSuccess: (data) => { setIsSubmitting(false) if (data.workflowRunId) { toast.success('Branch merge initiated!') addWorkflowRun(data.workflowRunId) // Track successful merge sendEvent({ action: 'branch_merge_completed', properties: { branchType: currentBranch?.persistent ? 'persistent' : 'preview', }, groups: { project: parentProjectRef ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown', }, }) } else { toast.info('No changes to merge') } }, onError: (error) => { setIsSubmitting(false) toast.error(`Failed to merge branch: ${error.message}`) // Track failed merge sendEvent({ action: 'branch_merge_failed', properties: { branchType: currentBranch?.persistent ? 'persistent' : 'preview', error: error.message, }, groups: { project: parentProjectRef ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown', }, }) }, }) const { mutate: deleteBranch, isPending: isDeleting } = useBranchDeleteMutation({ onSuccess: () => { toast.success('Branch closed successfully') router.push(`/project/${parentProjectRef}/branches`) // Track delete button click sendEvent({ action: 'branch_delete_button_clicked', properties: { origin: 'merge_page', }, groups: { project: parentProjectRef ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown', }, }) }, onError: (error) => { toast.error(`Failed to close branch: ${error.message}`) }, }) const handlePush = () => { if (!ref || !parentProjectRef) return pushBranch({ branchRef: ref, projectRef: parentProjectRef, }) } const handleCloseBranch = () => { if (!ref || !parentProjectRef) return deleteBranch({ branchRef: ref, projectRef: parentProjectRef, }) } const handleMerge = () => { if (!ref || !parentProjectRef) return setIsSubmitting(true) // Track merge attempt sendEvent({ action: 'branch_merge_submitted', groups: { project: parentProjectRef ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown', }, }) mergeBranch({ branchProjectRef: ref, baseProjectRef: parentProjectRef, migration_version: undefined, pgdelta: pgDeltaDiffEnabled, }) } const breadcrumbs = useMemo( () => [ { label: 'Merge requests', href: `/project/${project?.ref}/branches/merge-requests`, }, ], [project?.ref] ) const currentTab = (router.query.tab as string) || 'database' const navigationItems = useMemo(() => { const buildHref = (tab: string) => { const query: Record = { tab } if (currentWorkflowRunId) query.workflow_run_id = currentWorkflowRunId const qs = new URLSearchParams(query).toString() return `/project/[ref]/merge?${qs}` } return [ { label: 'Database', href: buildHref('database'), active: currentTab === 'database', }, { label: 'Edge Functions', href: buildHref('edge-functions'), active: currentTab === 'edge-functions', }, ] }, [currentWorkflowRunId, currentTab]) useEffect(() => { setWorkflowFinalStatus(null) }, [currentWorkflowRunId]) // If not on a preview branch or branch info unavailable, show notice if (!isBranch || !currentBranch) { return (

You can only review changes when on a preview branch

) } const hasGHProductionDeployEnabled = !!ghConnection && Boolean(mainBranch?.git_branch) return ( } subtitle={} breadcrumbs={breadcrumbs} primaryActions={ setShowConfirmDialog(true)} /> } size="full" className="h-full border-b-0 pb-0" >
{hasGHProductionDeployEnabled ? (

Branches should be managed via GitHub to prevent drifts in migrations from your repository's state. You may either move your schema changes to a GitHub pull request, or disable "Deploy to production" in the{' '} GitHub integration settings .

) : isBranchOutOfDateOverall && !currentWorkflowRunId ? ( ) : currentWorkflowRunId ? (
) : undefined } overrideAction={ hasCurrentWorkflowFailed ? ( ) : hasCurrentWorkflowCompleted ? ( ) : undefined } />
) : null} {navigationItems.map((item) => { const isActive = item.active !== undefined ? item.active : router.asPath.split('?')[0] === item.href return ( {item.label} ) })}
{currentTab === 'database' ? ( ) : ( )}
{ setShowConfirmDialog(false) handleMerge() }} onCancel={() => setShowConfirmDialog(false)} loading={isMerging || isSubmitting} />
) } MergePage.getLayout = (page) => ( {page} ) export default MergePage